#import "NuText.h"
#import <stdlib.h>
#import "WorkspaceManager.h"
#import "ClassManager.h"
#import "NuWraps.h"
#import "NuString.h"
#import "MenuManager.h"
#import "PrefsManager.h"
#import <strings.h>
#import <appkit/publicWraps.h>
#import <appkit/Scroller.h>
#import <appkit/ScrollView.h>
#import <appkit/Application.h>
#import <appkit/drag.h>
#import <appkit/Pasteboard.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/NXHelpPanel.h>
#import <appkit/NXBrowser.h>
#import <defaults/defaults.h>
#import "NuGraphicCell.h"
#import <appkit/Pasteboard.h>
#import <appkit/tiff.h>
#import <dpsclient/wraps.h>
#import <ctype.h>
#import <libc.h>
#import <objc/objc-load.h>


extern id Nu ;
extern char bigBuf[] ; // a "big" buffer for general use

#define ESC 27
#define CTRL_A (1)
#define CTRL_B (2)
#define CTRL_C (3)
#define CTRL_D (4)
#define CTRL_E (5)
#define CTRL_F (6)
#define CTRL_K (11)
#define CTRL_N (14)
#define CTRL_P (16)
#define CTRL_V (22)
//
// following procedures were "stolen" from
// Lee boynton's LispExample
/*
** NXBGetc - a text stream macro to get the previous character.
*/

typedef struct {
    id text;
    NXTextBlock *block;
} textInfo;

static char getPrevious(NXStream *s)
{
    textInfo *info = (textInfo *) s->info;

    NXTextBlock *block = info->block->prior;
    if (!block) {
	return EOF;
    }
    s->buf_base = block->text;
    s->buf_ptr = s->buf_base + block->chars;
    s->offset -= block->chars;
    info->block = block;
    return *(--s->buf_ptr);
}

#define NXBGetc(s) \
    (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
	*(--((s)->buf_ptr)) )


int
findMatchingOpenParen(id text, char thisChar, char thatChar, int curPos)
{ int count = 1;
  char ch;
  NXStream *s = [text stream];
  NXSeek(s, curPos, NX_FROMSTART);
  while ((ch = NXBGetc(s)) != EOF)
  { if(ch == thatChar && !(--count))
      return NXTell(s);
    else if (ch == thisChar)
      count++;
  }
  return -1;
}

char *
nextLine(char *text,char *lineBuf)
{ // fill lineBuf with a copy of the next line in text,
  // not including any terminating newLine.  Return NULL if
  // no line found, else return address of first character
  // following line 
  char *cursor = text ;
  int i = 0 ;
  if(*cursor == '\0')
    return NULL ;
  while(*cursor &&  *cursor != '\n')
  { lineBuf[i++] = *cursor ;
    cursor++ ;
  }
  lineBuf[i] = '\0' ;
  if(*cursor == '\n')
    return ++cursor ;
  else
    return cursor ;
}

int
findMatchingCloseParen(id text, char thisChar, char thatChar, int curPos)
{
    int count = 1;
    char ch;
    NXStream *s = [text stream];
    NXSeek(s, curPos, NX_FROMSTART);
    while ((ch = NXGetc(s)) != EOF) {
	if (ch == thatChar && !(--count))
	    return NXTell(s);
	else if (ch == thisChar)
	    count++;
    }
    return -1;
}

void highlightChar(id text, int charPosition)
{
    NXSelPt selStart, selEnd;
    [text getSel:&selStart :&selEnd];
    [text setSel:charPosition :charPosition+1];
    [[text window] flushWindow];
    NXPing();
    [[text window] disableFlushWindow];
    [text setSel:selStart.cp :selEnd.cp];
    [[text window] reenableFlushWindow];
}


@implementation NuText:Text

+ initialize ;
{ // super will register a bunch of directives
  [super initialize] ;
  // here we "re-register" the NeXTGraphic directive to make
  // NuGraphicCells (a subclass of the undocumented
  // NXGraphicCell) look after the NeXTGraphic RTFD directive. 
  [[self class] registerDirective: "NeXTGraphic" forClass:[NuGraphicCell class]] ;
  return self ;
}

- awake ;
{ // wait until everything has been unarchived, then do setup stuff
  [super awake] ;
  [self perform:@selector(awakeAfterDelay:) 
	with:self  afterDelay:1 cancelPrevious:YES];
  return self ;
}

- awakeAfterDelay: sender ;
{ const char *pBoardTypes[] = {NXFilenamePboardType,NULL} ;
  int pBoardTypeCount = 1 ; // elements in types
  if([window isKindOf: [WorkspaceManager class]])
  { [self setEditable: YES] ;
    [self setGraphicsImportEnabled: YES] ;
    // register to accept icons
    [self registerForDraggedTypes:(const char *const *)pBoardTypes
                 count:pBoardTypeCount] ;
  }
  else 
  { [self setEditable: NO] ;
  } 
  return self ;
}


- evalSelection:sender
{ // compile and run the workspace selection
  // Define default name of class:
#define NUEVAL "NuEval"
  char tmpFile[] = NUEVAL, fullPath[128] ;
  NXSelPt start, end ;
  FILE *fp ;
  int textLen  ;
  char *headerBuf, *trueBuf, *textBuf, *cursor ;
  BOOL understandsMessage ;

  understandsMessage = [window respondsTo: @selector(message:)] ;
  if(understandsMessage)
    [window message: "Evaluating..."] ;
  // get the selected text
  [self getSel:&start :&end] ;
  textLen = end.cp-start.cp ;
  textBuf = trueBuf = (char *) malloc(textLen+1) ; 
  [self getSubstring:textBuf start:start.cp length:textLen] ;
  textBuf[textLen] = '\0' ;
  // now find out if we have any header
  headerBuf = textBuf ;
  while(isspace(*headerBuf))
    headerBuf++ ;  // scan past any initial whiteSpace
  if(*headerBuf == '#') // then we have a header
  { char messBuf[256] ;
    cursor = headerBuf ;
    while(cursor = nextLine(cursor,bigBuf))
    {  // check for a load command
       if(!strncmp(bigBuf,"#pragma load ",13))
       { if(understandsMessage)
         { sprintf(messBuf,"Preloading %s",&bigBuf[13]) ;
           [window message: messBuf] ;
         }
         [Nu loadIfNeeded: &bigBuf[13]] ;
      }
       else if(!strlen(bigBuf))
         break ;
    }
    if(cursor == NULL)
    { // No body to evaluate      free(textBuf) ;
      if(understandsMessage)
       [window message: ""] ;
      return self ;
    }
    textBuf = cursor ;
    cursor-- ;
    *cursor = '\0' ; // chop string into 2 pieces    
  }
  else
    cursor = headerBuf = "" ;   // no header at all

  // now hack together the file...
  sprintf(fullPath,"/tmp/%s.m",tmpFile) ;
  fp = fopen(fullPath,"w+") ;
  fprintf(fp,
   "%s\n"
   "#import \"Glyph.h\"\n"
   "#import \"Nutation.h\"\n"
   "@interface %s: Glyph\n"
   "+(void) doit ;\n"
   "@end\n\n"
   "@implementation %s\n"
   "+ (void) doit\n"
   "{\n"
   "#line 1\n"
   "%s\n"
   "}\n"
   "@end",
   headerBuf, tmpFile, tmpFile, textBuf) ;
  fflush(fp) ;
  fclose(fp) ;
  
  // free up alloc'ed memory
  if(headerBuf[0] != '\0') // string was cut into 2 pieces...
    *cursor = ' ' ;   // reassemble
  free(trueBuf) ; // trueBuf always points to beginning of malloc'ed string

  // Now compile and load the file
  if(understandsMessage)
    [window message: "Compiling..."] ;
  if([ClassManager compileClass: NUEVAL file: fullPath])
  { // compile succeeded, load it
    if(understandsMessage)
      [window message: "Loading..."] ;
    if([ClassManager load: NUEVAL])
    { // load succeeded, execute it
      if(understandsMessage)
        [window message: "Executing..."] ;
      objc_msgSend(objc_getClass(tmpFile),
         sel_getUid("doit")) ;
     if(understandsMessage)
        [window message: ""] ;
      // now remove the temporary class
      [ClassManager unload: tmpFile] ;
    }
  }
  unlink(tmpFile) ;
  return self ;
}


- insertLink: sender ;
{ [window setDocEdited: YES] ;
  return [self replaceSelWithCell: [[NXLinkCell alloc] initForView: self]] ;
}

- insertMarker: sender ;
{ [window setDocEdited: YES] ;
  return [self replaceSelWithCell: [[NXMarkerCell alloc] initForView: self]] ;
}

- keyDown:(NXEvent *)theEvent
{  // provide some emacs-style key bindings
    int i ;
    static unsigned short val = '\0', lastChar = '\0' ;
    NXSelPt selStart, selEnd;
    [self getSel:&selStart :&selEnd];
    lastChar = val ; // val was set in "previous" call of keyDown..
    val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8);
    switch (val) {
	case ESC: // don't want ESC to cause a beep
	    return self ;
	case '<':
	    if(lastChar == ESC)
	    { [self setSel: 1 :1] ;
	      [self scrollSelToVisible] ;
	      return self ;
	    }
	    break ;
	case '>':
	    if(lastChar == ESC)
	    { i = [self textLength] ; --i ;
	      [self setSel: i :i] ;
	      [self scrollSelToVisible] ;
	      return self ;
	    }
	    break ;
	case ')':
	    i = findMatchingOpenParen(self,')','(',selEnd.cp);
	    [super keyDown:theEvent];
	    if (i >= 0)
		highlightChar(self,i);
	    return self;
	case '}':
	    i = findMatchingOpenParen(self,'}','{',selEnd.cp);
	    [super keyDown:theEvent];
	    if (i >= 0)
		highlightChar(self,i);
	    return self;
	case ']':
	    i = findMatchingOpenParen(self,']','[',selEnd.cp);
	    [super keyDown:theEvent];
	    if (i >= 0)
		highlightChar(self,i);
	    return self;
	case CTRL_A: // beginning of line
            i = [self lineFromPosition:selStart.cp] ;
	    i = [self positionFromLine: i] ;
	    [self setSel:i :i];
	    return self;
	case CTRL_E: // end of line
	    i = [self lineFromPosition: selStart.cp] ;
	    i = [self positionFromLine: i + 1] ; // beginning of next line
	    [self setSel:i -1 :i - 1];
	    return self;
	case CTRL_B:// backwards one char
	    if (selStart.cp > 1)
		[self setSel:selStart.cp - 1 :selStart.cp -1];
	    else
		NXBeep();
	    return self;
	case CTRL_F: // forwards one char
	    if (selEnd.cp < [self textLength])
	    	[self setSel:selEnd.cp+1 :selEnd.cp+1];
	    else
		NXBeep();
	    return self;
	case CTRL_D: // delete char under cursor
	    [self setSel:selEnd.cp :selEnd.cp+1];
	    [self replaceSel:""];
	    [self setSel:selStart.cp :selStart.cp] ;
	    return self;
	case CTRL_K: // clear to end of line 
	    i = [self lineFromPosition: selStart.cp] ;
	    i = [self positionFromLine: i + 1] ; // beginning of next line
	    [self setSel:selStart.cp :--i];
	    [self replaceSel:""];
	    [self setSel:selStart.cp :selStart.cp] ;
	    return self;
 	case CTRL_P: // move directly up one line...fake down arrow
	    theEvent->data.key.charCode = 173 ;
	    theEvent->data.key.charSet = 1 ;
	    theEvent->flags = NX_NUMERICPADMASK ;
	    break ;
	case CTRL_N: // move directly down one line...fake up arrow
	    theEvent->data.key.charCode = 175 ;
	    theEvent->data.key.charSet = 1 ;
	    theEvent->flags = NX_NUMERICPADMASK ;
	    break ;
	case 'v':    // move up one screenful
	    if(lastChar != ESC)
		break ;
	case CTRL_V: // move down one screenful
	    { id scroller ;
	      NXRect aRect ;
	      NXEvent fakeEvent ;
	      fakeEvent = *theEvent ;
	      fakeEvent.type = NX_MOUSEDOWN ;
	      fakeEvent.data.mouse.reserved = 18 ;
	      fakeEvent.data.mouse.eventNum = 9999 ;
	      fakeEvent.data.mouse.click = 1 ;
	      // fakeEvent.data.mouse.unused = 0 ;
	      fakeEvent.flags = NX_ALTERNATEMASK ; 
	      scroller = [[superview superview] vertScroller] ;
	      [scroller calcRect:&aRect forPart:
                  val == 'v'? NX_DECLINE :NX_INCLINE ] ;
	      [scroller convertPoint: &aRect.origin toView: nil] ;
	      fakeEvent.location = aRect.origin ;
              DPSPostEvent(&fakeEvent,1) ;
	      fakeEvent.type = NX_MOUSEUP ;
	      fakeEvent.time += 10 ; // 10 /68ths of a second
              DPSPostEvent(&fakeEvent,0) ;
	      return self ;
	    }
	default:
	    break;
    }
    return [super keyDown:theEvent];
}


- mouseDown: (NXEvent *) anEvent ;
{ // augment double-click behavior to
  // check for bracketed structures and quoted
  // items. augment command-click behaviour to
  // select between backquotes.
  NXSelPt start, end ;
  char openers[] = "{([<\"'/`" ;
  char closers[] = "})]>\"'/`" ;
  char c,d, *place ;
  int otherPos ;
  NXStream *aStream ;
  [super mouseDown: anEvent] ;
  if(anEvent->data.mouse.click == 2)
  { aStream = [self stream] ;
    [self getSel: &start :&end] ;
    if(start.cp > 0)
    { // NXSeek(aStream,start.cp-1,NX_FROMSTART) ; // worked in 2.0, changed
      NXSeek(aStream,start.cp,NX_FROMSTART) ; // to this for 3.0!
      c = NXGetc(aStream) ;
      if(place = index(closers,c)) // we have right side of a structure
      {  d = openers[(int) place - (int) closers] ;// grab matching char
         if( (otherPos = findMatchingOpenParen(self, c, d, start.cp -1))
           != -1)
        { if(c == '`') // put selection "inside" ` ... ` pair
            [self setSel: otherPos + 1 :start.cp - 1] ;
          else
            [self setSel: otherPos :start.cp + 1] ;
          return self ;
        }
      }
      else
      { NXSeek(aStream,start.cp,NX_FROMSTART) ;
        c = NXGetc(aStream) ;
        if(place = index(openers,c)) // we have a structure
        { d = closers[(int) place - (int) openers] ;
          if( (otherPos = findMatchingCloseParen(self, c, d, start.cp +1))
           != -1)
          { if(c == '`') // put selection "inside" ` ... ` pair
             [self setSel: start.cp +1 :otherPos-1] ;
           else
             [self setSel: start.cp :otherPos] ;
          }
        }
      }
    }
  }
  else if((anEvent->data.mouse.click == 1) &&
     (anEvent->flags & NX_COMMANDMASK))
  { // command click == search back for backquote (or beginning of text)
    // forward for backquote (or end ot text), then select. 
    int from, to ;
    aStream = [self stream] ;
    [self getSel: &start :&end] ;
    if(start.cp > 0)
    { from = start.cp - 1 ;
      NXSeek(aStream,from,NX_FROMSTART) ;
      while(from >= 1 && ((c = NXBGetc(aStream)) != '`'))
        from-- ;
      to = start.cp ;
      NXSeek(aStream,to,NX_FROMSTART) ;
      while(!NXAtEOS(aStream) && ((c = NXGetc(aStream)) != '`'))
        to++ ;
      [self setSel: from :to] ;
    }
  }
  return self ;
}

#define NX_NOBUTTONS (0)
#define NXSetWindowLevel _NXSetWindowLevel

- pasteScreen: sender ;
{ // grab a portion of the screen,
  // save it as a file, then paste
  // it into the rtfd
  id theWin, aWin, theImage ;
  char saveName[32] ;
  if([(theWin = [NXApp mainWindow]) isMemberOf: [WorkspaceManager class]])
  { NXRect aRect ;
    extern int NXSetWindowLevel(int, int);
   // get a rectangle from the user (using a procedure defined
   // in NuWraps.psw-defined procedure)
   grabScreen(&aRect.origin.x,&aRect.origin.y,
     &aRect.size.width, &aRect.size.height) ;
   // now make an invisible window to grab bits
   aWin = [[Window alloc]
     initContent: &aRect
     style:NX_PLAINSTYLE
     backing:NX_NONRETAINED
      buttonMask:NX_NOBUTTONS
     defer:NO] ;
    PSsetautofill(NO,[aWin windowNum]) ;
   NXSetWindowLevel([aWin windowNum],
       NX_MAINMENULEVEL);
   [aWin orderFront: 0] ;
   [[aWin contentView] lockFocus] ;
   theImage = [[NXImage alloc] init] ;
   [theImage setDataRetained:YES] ;
   aRect.origin.x = aRect.origin.y = 0.0 ; // convert screen to aWin
   [theImage useRepresentation:
     [[NXBitmapImageRep alloc] initData: NULL fromRect: &aRect]] ;
   [[aWin contentView] unlockFocus] ;
   [aWin free] ;
   sprintf(saveName,"%s%x.tiff", NUSCREENPASTE, (unsigned) time(NULL));
   [theImage setName: saveName] ;
   [self replaceSelWithCell:[[NXGraphicCell alloc] initIconCell: saveName]] ;
   [window setDocEdited: YES] ;
   [self setRTFD: YES] ;
   return self ;
  }
  else
    return self ; // ignore
}  


- showMarkers: sender ;
{ // here I use 2 undocumented Text functions, as gleaned
  // from IB...in order to control visibility or markers
  if([self markersVisible])
  { [self setMarkersVisible: NO] ;
    [sender setStringValue: "Show Markers"] ;
  }
  else
  { [self setMarkersVisible: YES] ;
    [sender setStringValue: "Hide Markers"] ;
  }
  return self ;
}


- (int) tag ;
{ return TEXTVIEWTAG ;
}

@end